/*
Copyright (c) 2014, Rick Wilson
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of 'Taareekah' nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import world.GenericTerrainMap;
import worldgeneration.WorldGenerator;


import util.AStar;
import util.AStarDistanceCalculator;
import util.AStarHeuristic;
import util.AStarNode;
import util.Point;
import util.BMPFile;
import util.BitmapPixelShader;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.SortedSet;
import java.util.Set;
import java.util.HashSet;
import java.util.TreeSet;
import java.util.Vector;
import java.util.PriorityQueue;
import java.util.List;

import gameobject.River;
import gameobject.GOManager;
import gameobject.Component;
import gameobject.GOHandle;
import parameters.*;

class Heuristic implements AStarHeuristic {
    private GenericTerrainMap elevation;
    public Heuristic(GenericTerrainMap elevation) {
        this.elevation = elevation;
    }
    public double estimateDistance(AStarNode from, AStarNode to) {
        return to.straightLineDistance(from);
    }
}

class DistanceCalculator implements AStarDistanceCalculator {
    private GenericTerrainMap elevation;
    
    public boolean areNeighbors(AStarNode from, AStarNode to) {
        int d_x = Math.abs(from.getX() - to.getX());
        int d_y = Math.abs(from.getY() - to.getY());
        if(d_x > 1 || d_y > 1) {
            return false;
        } else if(d_x == 0 && d_y == 0) {
            return false;
        }
        
        return true;
    }
    
    public DistanceCalculator(GenericTerrainMap elevation) {
        this.elevation = elevation;
    }
    public double distanceBetween(AStarNode from, AStarNode to) {
        if(areNeighbors(from, to)) {
            
            float height_delta = elevation.getValueAtPoint(to.getX(), to.getY()) - elevation.getValueAtPoint(from.getX(), from.getY());
            return 1.0 + (height_delta).div(4.0);
        }
        Logger.getLogger(DistanceCalculator.class.getName()).log(Level.INFO, "from and to arent neighbors " + from.getX() + ", " + from.getY() + "; " + to.getX() +", " + to.getY());
        return to.manhattanDistance(from);
            
    }
}
class Shader extends BitmapPixelShader {
    private Set<Point> river_points;
    
    public Shader(List<River> rivers) {
        river_points = new TreeSet<Point>();
        for (int river_index = 0; river_index < rivers.size(); river_index++) {
            List<Point> points = rivers.get(river_index).callMethod("getPath");
            river_points.addAll(points);
        }
        
        
    }
    public int[] getColorAtPoint(int x, int y) {
        int[] ret = new int[3];
        ret[0] = ret[1] = ret[2] = 0;
        Point p = new Point(x, y);
        if (river_points.contains(p)) {
            ret[2] = 255;
        }
        return ret;
    }
}
class RiverGenerator {
    private List<River> river_components;
    private int size;
    private long seed;
    public List<River> getRivers() {
        return river_components;
    }
    public boolean saveRiversAsBitmap(String filename) throws Exception {
        Shader shader = new Shader(river_components);
        BMPFile.saveBMP(filename, size, size, shader);
        
        return true;
    }

private class Vertex implements Comparable<Vertex> {
    private int x, y;
    private float height;
    
    public Vertex(int x, int y, float height) {
        this.x = x;
        this.y = y;
        this.height = height;
    }
    
    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
    public float getHeight() {
        return height;
    }
    public boolean equals(Vertex o) {
        if (x == o.getX() && y == o.getY()) {
            return true;
        }
        return false;
    }
    
    public double distance(Vertex o) {
        return Math.sqrt(Math.pow((o.getX() - getX()), 2.0) + Math.pow((o.getY() - getY()), 2));
    }
    public int compareTo(Vertex o) 
    {
        int val = Math.round(this.height - o.getHeight());
        if (val != 0) {
            return val;
        }
        
        if (this.x < o.getX()) {
            return -1;
        } else if (this.x == o.getX() && this.y < o.getY()) {
            return -1;
        } else if (this.x == o.getX() && this.y == o.getY()) {
            return 0;
        }
        
        return 1;
    }
}
    private void addRiverFromPath(AStar a_star, WorldGenerator gen, GOManager gomanager) {
        if (a_star.getPathLength() <= 0) {
            Logger.getLogger(RiverGenerator.class.getName()).log(Level.INFO, "Invalid path length");
            return;
        }
        
        GOHandle river_handle = gomanager.createGO("River");
        River river = (River) river_handle.getComponent("River");
        
        for(int i = 0; i < a_star.getPathLength(); i++) {
            river.callMethod("addPoint", a_star.getPointOfPath(i));
            
        }
        river_components.add(river);
    }
    public boolean load(int num_rivers, int size, long seed, GenericTerrainMap elevation, WorldGenerator gen, GOManager gomanager) {
        this.size = size;
        river_components = new ArrayList<River>();
        TreeSet sorted = new TreeSet<Vertex>();
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {

                sorted.add(new Vertex(i, j, elevation.getValueAtPoint(i, j)))
            }
        }
        Vertex[] belowsealevel = sorted.headSet(new Vertex(0,0, 100.0f), false).toArray(new Vertex[0]);
        Vertex[] topthreefifths = sorted.tailSet(new Vertex(0,0, 170.0f), false).toArray(new Vertex[0]);
        Random rand = new Random(seed);
        AStar a_star = new AStar(new DistanceCalculator(elevation), new Heuristic(elevation));
        for (int i = 0; i < num_rivers; i++) {
            double distance = 100000.0;
            Vertex start, stop;
            while (distance > size.toFloat().div(2)) {
                start = topthreefifths[rand.nextInt(topthreefifths.length)];
                stop = belowsealevel[rand.nextInt(belowsealevel.length)];
                distance = start.distance(stop);
            }
            a_star.findPath(start.getX(), start.getY(), stop.getX(), stop.getY(), size);

            addRiverFromPath(a_star, gen, gomanager);
        }
        return true;
    }
    
}
int num_rivers = scriptparameters.getChild("numrivers").getValueAsInt();
RiverGenerator river_generator = new RiverGenerator();
river_generator.load(num_rivers, worldgenerator.getWorldSize(), worldgenerator.getSeed(), worldgenerator.getMap("elevation"), worldgenerator, gomanager);
river_generator.saveRiversAsBitmap("rivers.bmp");
